﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Nature.Numerics
{
    /// <summary>
    /// Provides a unified way of converting types of values to other types, as well as for accessing standard values and subproperties.
    /// </summary>
    public class NumericalTypeConverter : TypeConverter
    {
        /// <summary>
        /// Double presision value pattern
        /// </summary>
        public const string ValuePattern = @"(?xi)[+-]?\s*(?:(?:(?:\d+\.?\d*)|(?:\.\d+))(?:[edf][+-]?\d{1,3})?)";

        /// <summary>
        /// RegularExpression object matching the string representation of double presision values
        /// </summary>
        public static readonly Regex ValueRegex = new Regex(ValuePattern);

        /// <summary>
        /// Converts the given string to an array of double presision values
        /// </summary>
        /// <param name="arrayString">String representation of the one dimensional array</param>
        /// <returns>Array of double presision values</returns>
        public static double[] ConvertToArray(string arrayString)
        {
            return Regex.Matches(arrayString, ValuePattern)
                .Cast<Match>()
                .Select(m => Regex.Replace(m.Value, "(?i)[df]", "e"))
                .Select(s => Double.Parse(s))
                .ToArray();
        }

        /// <summary>
        /// Converts the given string to a matrix of double presision values
        /// </summary>
        /// <param name="matrixString">String representation of the one matrix of double presision values</param>
        /// <returns>Matrix of double presision values</returns>
        public static double[][] ConvertToMatrix(string matrixString)
        {
            double[] values = Regex.Matches(matrixString, ValuePattern)
                .Cast<Match>()
                .Select(m=> Regex.Replace(m.Value, "(?i)[df]", "e") )
                .Select(s=> Double.Parse(s))
                .ToArray();
            int rank = (int)Math.Sqrt(values.Length);
            if (rank * rank != values.Length)
            {
                throw new System.ArgumentException();
            }
            double[][] matrix = new double[rank][];
            for (int i = 0; i < rank; ++i)
            {
                matrix[i] = new double[rank];
                for (int j = 0; j < rank; ++j)
                {
                    matrix[i][j] = values[i * rank + j];
                }
            }
            return matrix;
        }

        /// <summary>
        /// Converts the given string representing a matrix and an array to the string representing 
        /// the product vector
        /// </summary>
        /// <param name="matrixString">String representation of the one matrix of double presision values</param>
        /// <param name="vectorString">String representation of the one dimensional array</param>
        /// <returns>String representation of the matrix - vector multiplication operation</returns>
        public static string ConvertMatrixVectorProductString(string matrixString, string vectorString)
        {
            double[][] a = NumericalTypeConverter.ConvertToMatrix(matrixString);
            double[] x = NumericalTypeConverter.ConvertToArray(vectorString);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < x.Length; ++i)
            {
                double[] row = a[i];
                double sum = 0.0;
                for (int j = 0; j < x.Length; ++j) { sum += row[j] * x[j]; }
                if (i > 0) { sb.AppendFormat("   "); }
                sb.AppendFormat("{0}", sum);
            }
            return sb.ToString();
        }


        public static double[][] AllocateMatrix(int nva)
        {
            if (nva < 0) { throw new ArgumentOutOfRangeException(); }
            double[][] a = new double[nva][];
            for (int iva = 0; iva < nva; ++iva)
            {
                a[iva] = new double[nva];
            }
            return a;
        }
    }
}
